#include "Logger.hpp"
#include "LogFile.hpp"
#include "../../base/TimeUnit.hpp"

#include <cstdlib>
#include <cstring>
#include <chrono>
#include <cassert>

#ifdef LOGGER_BUFFER_SIZE
#undef LOGGER_BUFFER_SIZE
#define LOGGER_BUFFER_SIZE 4096
#elif
#define LOGGER_BUFFER_SIZE 4096
#endif


#define DEBUF       "[debug]    "
#define INFO        "[info]     "
#define WARN        "[warn]     "
#define ERROR       "[error]    "

enum LOG_TYPE {
    TYPE_DEBUG,
    TYPE_INFO,
    TYPE_WARN,
    TYPE_ERROR,
};

#define LOG_INIT(type, s)               \
switch (type)                           \
{                                       \
case TYPE_DEBUG:    s=DEBUF;    break;  \
case TYPE_INFO:     s=INFO;     break;  \
case TYPE_WARN:     s=WARN;     break;  \
case TYPE_ERROR:    s=ERROR;    break;  \
default:                        break;  \
};

using namespace DBServer::Log;

Logger::Logger(int _roll_size, int _flush_inteval)
:   roll_size(_roll_size), 
    flush_interval(_flush_inteval),
    latch(1),
    cur_buf(new Buffer),
    next_buf(new Buffer),
    running(false),
    clock()
{
    buffers.reserve(16);
}

Logger::~Logger()
{
    if (running) 
        stop();
}

void Logger::start(int log_buffer_size)
{
    running = true;
    t = std::move(std::thread([this, log_buffer_size]{
        this->thread_func(log_buffer_size);
    }));
    latch.wait();
}

void Logger::stop()
{
    running = false;
    cond.notify_all();
    t.join();
}

void Logger::thread_func(int log_buffer_size)
{
    assert(running == true);
    latch.count_down();
    // initialize a FileLog Object
    LogFile log_file{log_buffer_size};
    BufferPtr new_buf_1(new Buffer), new_buf_2(new Buffer);
    BufferVector buffers_to_write;
    buffers_to_write.reserve(16);

    while (running)
    {
        assert(new_buf_1 && new_buf_1->size() == LOGGER_BUFFER_SIZE);
        assert(new_buf_2 && new_buf_2->size() == LOGGER_BUFFER_SIZE);
        assert(buffers_to_write.empty());

        {
            std::unique_lock<std::mutex> lock(mtx);
            if (buffers.empty())
            {
                cond.wait_for(lock,(std::chrono::seconds(flush_interval)));
            }
            buffers.emplace_back(std::move(cur_buf));
            cur_buf = std::move(new_buf_1);
            buffers_to_write.swap(buffers);
            if (!next_buf)
            {
                next_buf = std::move(new_buf_2);
            }
        }

        assert(!buffers_to_write.empty());

        if (buffers_to_write.size() > 25) 
        {
            char buf[256];
            clock.cur();
            std::string s = ERROR;
            s += clock.format();
            s += "Dropped log message larger buffer\n";
            // write into the buffer
            log_file.append(s);
            buffers_to_write.erase(buffers_to_write.begin()+2, buffers_to_write.end());
        }

        for (auto& buffer : buffers_to_write)
        {
            auto buf = buffer->get();
            std::string tmp(buf);
            log_file.append(tmp);
        }

        if (buffers_to_write.size() > 2)
        {
            buffers_to_write.resize(2);
        }

        if (!new_buf_1)
        {
            assert(!buffers_to_write.empty());
            new_buf_1 = std::move(buffers_to_write.back());
            buffers_to_write.pop_back();
            new_buf_1->clear();
        }
        if (!new_buf_2)
        {
            assert(!buffers_to_write.empty());
            new_buf_2 = std::move(buffers_to_write.back());
            buffers_to_write.pop_back();
            new_buf_2->clear();
        }

        buffers_to_write.clear();
        // flush the buffer
        log_file.flush();
    }
}

void Logger::append(std::string&& s_msg, int type)
{
    log_factory(s_msg, type);
    const char *msg = s_msg.c_str();
    int _size = s_msg.size();
    std::unique_lock<std::mutex> lock(this->mtx);
    if (cur_buf->size() > _size) 
    {
        cur_buf->append(msg, _size);
    }
    else
    {
        buffers.push_back(std::move(cur_buf));
        if (next_buf)
        {
            cur_buf = std::move(next_buf);
        }
        else
        {
            cur_buf.reset(new Buffer);
        }
        cur_buf->append(msg, _size);
        cond.notify_all();
    }
}

auto Logger::log_factory(std::string& msg, int type) -> void
{
    std::string s;
    LOG_INIT(type, s);
    clock.cur();
    s += clock.format();
    msg = s + msg;
}

void Logger::debug(std::string&& s)
{
    append(std::move(s), TYPE_DEBUG);
}

void Logger::info(std::string&& s)
{
    append(std::move(s), TYPE_INFO);
}

void Logger::warn(std::string&& s)
{
    append(std::move(s), TYPE_WARN);
}

void Logger::error(std::string&& s)
{
    append(std::move(s), TYPE_ERROR);
}

#if 0

#include <iostream>

using namespace std;

int main()
{
    Logger log("log.log", 100);

    std::string s1 = "test logger\n";
    std::string s2 = "test logger\n";
    std::string s3 = "test logger\n";
    std::string s4 = "test logger\n";

    log.start();

    log.debug(std::move(s1));
    log.info(std::move(s2));
    log.warn(std::move(s3));
    log.error(std::move(s4));

    log.stop();
}

#endif